local function createprefabs(customprefabs)
    local prefabs =
    {
        "nightmarefuel",
        "shadowheart",
        "armor_sanity",
        "nightsword",
    }

    for i, v in ipairs(customprefabs) do
        if not table.contains(prefabs, v) then
            table.insert(prefabs, v)
        end
    end
    return prefabs
end

local function createassets(name)
    return
    {
        Asset("ANIM", "anim/" .. name .. ".zip"),
        Asset("ANIM", "anim/" .. name .. "_upg_build.zip"),
    }
end

local bishopfxassets =
{
    Asset("ANIM", "anim/shadow_bishop_fx.zip"),
}
local witch_shadow_knight_noattackbrain = require("brains/witch_shadow_knight_noattackbrain")
local brains =
{
    ["shadow_rook"] = require("brains/shadow_rookbrain"),
    ["shadow_knight"] = require("brains/shadow_knightbrain"),
    ["shadow_bishop"] = require("brains/shadow_bishopbrain"),
}

local PHYS_RADIUS =
{
    ["shadow_rook"]   = 1.6,
    ["shadow_knight"] = .25,
    ["shadow_bishop"] = .3,
}

--------------------------------------------------------------------------

local function lootsetfn(lootdropper)
    local loot = {}
    for i = 1, math.random(2, 3) do
        table.insert(loot, "nightmarefuel")
    end
    table.insert(loot, "shadowheart")
    table.insert(loot, "nightmarefuel")
    --TODO: replace with shadow equipment drops
    table.insert(loot, "armor_sanity")
    table.insert(loot, "nightsword")
    if IsSpecialEventActive(SPECIAL_EVENTS.WINTERS_FEAST) then
        table.insert(loot, GetRandomBasicWinterOrnament())
    end
    for i = 1, 30 do
        local numcandies = 1 + math.random(2) + 2
        local candytypes = { math.random(NUM_HALLOWEENCANDY), math.random(NUM_HALLOWEENCANDY), math.random(
            NUM_HALLOWEENCANDY) }
        for k = 1, numcandies do
            table.insert(loot, "halloweencandy_" .. GetRandomItem(candytypes))
        end
        if math.random() <= TUNING.HALLOWEEN_PUMPKINCARVER_PIGKING_TRADE_CHANCE then
            table.insert(loot, "pumpkincarver" .. math.random(NUM_HALLOWEEN_PUMPKINCARVERS))
        end
    end


    lootdropper:SetLoot(loot)
end
local function lootsetfn_default(lootdropper)
    local loot = {}
    for i = 1, math.random(2, 3) do
        table.insert(loot, "nightmarefuel")
    end


    lootdropper:SetLoot(loot)
end
--------------------------------------------------------------------------

local function retargetfn(inst)
    --retarget nearby players if current target is fleeing or not a player
    local target = inst.components.combat.target
    if target ~= nil then
        local dist = TUNING[string.upper("SHADOW_KNIGHT")].RETARGET_DIST
        if target:HasTag("player") and inst:IsNear(target, dist) or not inst:IsNearPlayer(dist, true) then
            return
        end
        target = nil
    end

    local x, y, z = inst.Transform:GetWorldPosition()
    local players = FindPlayersInRange(x, y, z, TUNING.SHADOWCREATURE_TARGET_DIST, true)
    local rangesq = math.huge
    for i, v in ipairs(players) do
        local distsq = v:GetDistanceSqToPoint(x, y, z)
        if distsq < rangesq and inst.components.combat:CanTarget(v) then
            rangesq = distsq
            target = v
        end
    end
    return target, true
end

local function ShareTargetFn(dude)
    return dude:HasTag("shadowchesspiece") and not dude.components.health:IsDead()
end

local function OnAttacked(inst, data)
    inst.components.combat:SetTarget(data.attacker)
    inst.components.combat:ShareTarget(data.attacker, 30, ShareTargetFn, 1)
end

--------------------------------------------------------------------------

local function PushMusic(inst)
    if ThePlayer ~= nil and ThePlayer:IsNear(inst, 30) then
        ThePlayer:PushEvent("triggeredevent", { name = "shadowchess" })
    end
end

local function OnMusicDirty(inst)
    --Dedicated server does not need to trigger music
    if not TheNet:IsDedicated() then
        if inst._music:value() then
            if inst._musictask == nil then
                inst._musictask = inst:DoPeriodicTask(1, PushMusic, 0)
            end
        elseif inst._musictask ~= nil then
            inst._musictask:Cancel()
            inst._musictask = nil
        end
    end
end

local function StartMusic(inst)
    if not (inst._music:value() or inst.components.health:IsDead()) then
        inst._music:set(true)
        OnMusicDirty(inst)
    end
end

local function StopMusic(inst)
    if inst._music:value() then
        inst._music:set(false)
        OnMusicDirty(inst)
    end
end

--------------------------------------------------------------------------
local function WantsToLevelUp(inst)
    return false
end
--------------------------------------------------------------------------

local MAX_LEVEL = 3

local function commonlevelup(inst, overridelevel)
    if inst.components.health:IsDead() then
        return
    end
    local level = math.min(overridelevel or (inst.level + 1), MAX_LEVEL)
    if level ~= inst.level then
        inst.level = level

        local tunings = TUNING[string.upper("SHADOW_KNIGHT")]
        local scale = tunings.LEVELUP_SCALE[level]

        local x, y, z = inst.Transform:GetWorldPosition()
        inst.Transform:SetScale(scale, scale, scale)
        inst.Physics:SetCapsule(PHYS_RADIUS[string.lower("SHADOW_KNIGHT")] * scale, 1)
        inst.Physics:Teleport(x, y, z)

        inst.AnimState:SetMultColour(1, 1, 1, 0.5 + (0.12 * (level - 1)))

        inst.components.health:SetMaxHealth(tunings.HEALTH[level])

        if level > 1 then
            inst:AddTag("epic")
            inst:AddTag("noepicmusic")
            StartMusic(inst)
            inst.sounds.levelup = "dontstarve/sanity/transform/three"

            if level < 3 then
                inst:AddTag("smallepic")
            else
                inst:RemoveTag("smallepic")
            end
        else
            inst:RemoveTag("epic")
            inst:RemoveTag("smallepic")
            inst:RemoveTag("noepicmusic")
            StopMusic(inst)
            inst.sounds.levelup = "dontstarve/sanity/transform/two"
        end

        return level, scale
    end
end

local function knightlevelup(inst, overridelevel)
    local level, scale = commonlevelup(inst, overridelevel)
    if level ~= nil then
        inst.components.locomotor.walkspeed = TUNING.SHADOW_KNIGHT.SPEED[level] / scale

        inst.components.combat:SetDefaultDamage(TUNING.SHADOW_KNIGHT.DAMAGE[level])
        inst.components.combat:SetRange(TUNING.SHADOW_KNIGHT.ATTACK_RANGE * scale,
            TUNING.SHADOW_KNIGHT.ATTACK_RANGE_LONG * scale)
        inst.components.combat:SetAttackPeriod(TUNING.SHADOW_KNIGHT.ATTACK_PERIOD[level])

        if level > 1 then
            local suffix = tostring(level - 1)
            inst.AnimState:OverrideSymbol("arm", "shadow_knight_upg_build", "arm" .. suffix)
            inst.AnimState:OverrideSymbol("ear", "shadow_knight_upg_build", "ear" .. suffix)
            inst.AnimState:OverrideSymbol("face", "shadow_knight_upg_build", "face" .. suffix)
            inst.AnimState:OverrideSymbol("head", "shadow_knight_upg_build", "head" .. suffix)
            inst.AnimState:OverrideSymbol("leg_low", "shadow_knight_upg_build", "leg_low" .. suffix)
            inst.AnimState:OverrideSymbol("neck", "shadow_knight_upg_build", "neck" .. suffix)
            inst.AnimState:OverrideSymbol("spring", "shadow_knight_upg_build", "spring" .. suffix)
        else
            inst.AnimState:ClearAllOverrideSymbols()
        end

        inst.sounds.attack = "dontstarve/sanity/knight/attack_" .. tostring(level)
    end
end
local function noLevelUp(...)

end
--------------------------------------------------------------------------
local AREAATTACK_MUST_TAGS = { "_combat" }
local excludetags_AreaAttack = { "INLIMBO", }

function Witch_DoAreaAttack(centerinst, attacker, range, validfn, attackfn, data)
    if not centerinst then return end
    data = data or {}
    local centerpos = centerinst
    if not centerinst.x then
        centerinst = centerinst:GetPosition()
        centerpos = centerinst
    end
    local combat = attacker.components.combat
    local hitcount = 0
    local x, y, z = centerpos:Get()
    local ents = TheSim:FindEntities(x, y, z, range, AREAATTACK_MUST_TAGS, excludetags_AreaAttack)
    for i, ent in ipairs(ents) do
        if (data.attackself or ent ~= attacker) and
            combat:IsValidTarget(ent) and
            combat:CanTarget(ent) and
            (validfn == nil or validfn(ent, attacker)) then
            if attackfn then
                attackfn(ent, attacker, data)
            else
                local dmg, spdmg = combat:CalcDamage(ent, data.weapon, combat.areahitdamagepercent)
                dmg = data.dmg or dmg
                spdmg = data.spdmg or spdmg
                ent.components.combat:GetAttacked(combat.inst, dmg, data.weapon, data.stimuli, spdmg)
            end
            hitcount = hitcount + 1
        end
    end

    return hitcount
end

--------------------------------------------------------------------------
local function DoSkill1(inst)
    --幻惑魔法：生成2个三阶段骑士（shadow_knight），生成的三阶段骑士不会自爆
    local x, y, z = inst.Transform:GetWorldPosition()
    for i = 1, 2 do
        local shadow_knight = SpawnPrefab("shadow_knight")
        shadow_knight.Transform:SetPosition(x + 10 * math.random() - 5, y, z + 10 * math.random() - 5)
        shadow_knight:LevelUp(3)
        shadow_knight.OnEntitySleep = nil
        shadow_knight.leader = inst
    end
end
local function Onhealthdelta(inst, data)
    local newpercent = data and data.newpercent or 1
    if newpercent <= 0.2 then
        inst:DoSkill2()
    end
end
local function DoSkill2(inst)
    if inst.has_done_skill2 then return end
    local range = 20
    --净罪的大炎
    local current = inst.components.health.currenthealth or 8100
    local x, y, z = inst.Transform:GetWorldPosition()
    local ents = TheSim:FindEntities(x, y, z, range)
    for i, ent in ipairs(ents) do
        if ent.components.burnable then
            ent.components.burnable:Ignite()
        end
    end
    Witch_DoAreaAttack(inst, inst, range, nil, function(ent, attacker)
        local inventory = ent.components.inventory
        local percent = 1
        for k, v in pairs(inventory and inventory.equipslots or {}) do
            if v.prefab == "blueamulet" then
                percent = 0.25
            end
        end
        ent.components.combat:GetAttacked(attacker, current * 10 * percent)
    end)
    inst.has_done_skill2 = true
    if not inst.components.health:IsDead() then
        inst.components.health:Kill()
    end
end
local follower_prefab = {
    ["witch_shadow_knight_walker"] = 15,
    ["witch_shadow_knight_lighter"] = 15,
    ["witch_shadow_knight_ouster"] = 15,
    ["witch_birchnutdrake"]= 15,
    ["witch_shadow_knight_leader"] = 2,--这个确定要十个？这个自己每个也会召唤十个
}
local function DoSkill3(inst)
    --生成的时候 召唤 自身的每个使魔 15个
    for k, v in pairs(follower_prefab) do
        for i = 1, v do
            local follower = SpawnPrefab(k)
            follower.leader = inst
            follower.Transform:SetPosition(inst.Transform:GetWorldPosition())
        end
    end
end
--------------------------------------------------------------------------


local function onsave(inst, data)
    data.level = inst.level > 1 and inst.level or nil
end

local function onpreload(inst, data)

end

--------------------------------------------------------------------------

local function OnEntityWake(inst)
    if inst._despawntask ~= nil then
        inst._despawntask:Cancel()
        inst._despawntask = nil
    end
end

local function OnDespawn(inst)
    inst._despawntask = nil
    if inst:IsAsleep() and not inst.components.health:IsDead() then
        inst:Remove()
    end
end

local function OnEntitySleep(inst)
    if inst._despawntask ~= nil then
        inst._despawntask:Cancel()
    end
    inst._despawntask = inst:DoTaskInTime(TUNING.SHADOW_CHESSPIECE_DESPAWN_TIME, OnDespawn)
end

--------------------------------------------------------------------------

local function commonfn(name, sixfaced, brain)
    local inst = CreateEntity()

    inst.entity:AddTransform()
    inst.entity:AddAnimState()
    inst.entity:AddSoundEmitter()
    inst.entity:AddNetwork()

    MakeCharacterPhysics(inst, 10, PHYS_RADIUS[name])
    RemovePhysicsColliders(inst)
    inst.Physics:SetCollisionGroup(COLLISION.SANITY)
    --inst.Physics:CollidesWith(COLLISION.SANITY)
    inst.Physics:CollidesWith(COLLISION.WORLD)

    if sixfaced then
        inst.Transform:SetSixFaced()
    else
        inst.Transform:SetFourFaced()
    end

    inst:AddTag("monster")
    inst:AddTag("hostile")
    inst:AddTag("notraptrigger")
    inst:AddTag("shadowchesspiece")
    inst:AddTag("shadow_aligned")

    inst.AnimState:SetBank(name)
    inst.AnimState:SetBuild(name)
    inst.AnimState:PlayAnimation("idle_loop")
    inst.AnimState:SetMultColour(1, 1, 1, .5)
    inst.AnimState:SetFinalOffset(1)
    inst.AnimState:UsePointFiltering(true)

    inst._music = net_bool(inst.GUID, "shadowchesspiece._music", "musicdirty")

    inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        inst:ListenForEvent("musicdirty", OnMusicDirty)

        return inst
    end


    inst:AddComponent("locomotor") -- locomotor must be constructed before the stategraph
    inst.components.locomotor:SetTriggersCreep(false)
    inst.components.locomotor.pathcaps = { ignorecreep = true }

    inst:AddComponent("health")
    inst.components.health.nofadeout = true

    inst:AddComponent("combat")
    inst.components.combat:SetRetargetFunction(3, retargetfn)

    inst:AddComponent("explosiveresist")

    inst:AddComponent("lootdropper")
    inst.components.lootdropper:SetChanceLootTable("shadow_chesspiece")
    inst.components.lootdropper:SetLootSetupFn(lootsetfn)

    inst:AddComponent("sanityaura")
    inst.components.sanityaura.aura = -TUNING.SANITYAURA_LARGE

    inst:AddComponent("epicscare")
    inst.components.epicscare:SetRange(TUNING.SHADOW_CHESSPIECE_EPICSCARE_RANGE)

    inst:AddComponent("drownable")

    inst:ListenForEvent("attacked", OnAttacked)
    inst:ListenForEvent("death", StopMusic)

    inst.OnSave = onsave
    inst.OnPreLoad = onpreload

    inst.WantsToLevelUp = WantsToLevelUp

    inst.OnEntityWake = OnEntityWake
    inst.OnEntitySleep = OnEntitySleep

    inst.level = 1

    inst.sounds =
    {
        --common sounds
        death = "dontstarve/sanity/death_pop",
        levelup = "dontstarve/sanity/transform/two",
    }
    inst.sounds.attack = "dontstarve/sanity/knight/attack_1"
    inst.sounds.attack_grunt = "dontstarve/sanity/knight/attack_grunt"
    inst.sounds.die = "dontstarve/sanity/knight/die"
    inst.sounds.idle = "dontstarve/sanity/knight/idle"
    inst.sounds.taunt = "dontstarve/sanity/knight/taunt"
    inst.sounds.disappear = "dontstarve/sanity/knight/dissappear"
    inst.sounds.hit = "dontstarve/sanity/knight/hit_response"

    inst:SetStateGraph("SG" .. name)
    inst:SetBrain(brain or brains[name])

    return inst
end

local function knightfn()
    local inst = commonfn("shadow_knight")
    inst:AddTag("witch")
    if not TheWorld.ismastersim then
        return inst
    end

    inst.components.locomotor.walkspeed = TUNING.SHADOW_KNIGHT.SPEED[1]
    inst.components.health:SetMaxHealth(8100) --2. 生命值： 8100
    inst.components.combat:SetDefaultDamage(TUNING.SHADOW_KNIGHT.DAMAGE[1])
    inst.components.combat:SetAttackPeriod(TUNING.SHADOW_KNIGHT.ATTACK_PERIOD[1])
    inst.components.combat:SetRange(TUNING.SHADOW_KNIGHT.ATTACK_RANGE, TUNING.SHADOW_KNIGHT.ATTACK_RANGE_LONG)


    inst:ListenForEvent("healthdelta", Onhealthdelta)

    inst.LevelUp = knightlevelup

    inst:LevelUp(3)

    inst.DoSkill1 = DoSkill1
    inst.DoSkill2 = DoSkill2
    inst.DoSkill3 = DoSkill3
print("knightfn")
    inst.spawn_fn= DoSkill3
    return inst
end
local function PlayFireExplosionSound(inst)
    inst.SoundEmitter:PlaySound("dontstarve/creatures/hound/firehound_explo")
end
local function Spawnnightlight(inst)
    local x, y, z = inst.Transform:GetWorldPosition()
    local nightlight = SpawnPrefab("nightlight")
    nightlight.Transform:SetPosition(x, y, z)
end
local function walkerfn()
    local inst = commonfn("shadow_knight", nil, witch_shadow_knight_noattackbrain)
    inst:AddTag("witch_friend")
    if not TheWorld.ismastersim then
        return inst
    end

    inst.components.locomotor.walkspeed = TUNING.SHADOW_KNIGHT.SPEED[1]
    inst.components.health:SetMaxHealth(TUNING.SHADOW_KNIGHT.HEALTH[1])
    inst.components.combat:SetDefaultDamage(TUNING.SHADOW_KNIGHT.DAMAGE[1])
    inst.components.combat:SetAttackPeriod(TUNING.SHADOW_KNIGHT.ATTACK_PERIOD[1])
    inst.components.combat:SetRange(TUNING.SHADOW_KNIGHT.ATTACK_RANGE, TUNING.SHADOW_KNIGHT.ATTACK_RANGE_LONG)
    inst.components.combat.canattack = false


    inst.LevelUp = noLevelUp

    inst.components.lootdropper:SetChanceLootTable('hound_fire')
    inst.components.lootdropper:SetLootSetupFn(lootsetfn_default)

    inst:ListenForEvent("death", PlayFireExplosionSound)

    return inst
end
local function trackerfn()
    local inst = commonfn("shadow_knight")
    inst:AddTag("witch_friend")
    if not TheWorld.ismastersim then
        return inst
    end

    inst.components.locomotor.walkspeed = TUNING.SHADOW_KNIGHT.SPEED[1]
    inst.components.health:SetMaxHealth(TUNING.SHADOW_KNIGHT.HEALTH[1])
    inst.components.combat:SetDefaultDamage(TUNING.SHADOW_KNIGHT.DAMAGE[1])
    inst.components.combat:SetAttackPeriod(TUNING.SHADOW_KNIGHT.ATTACK_PERIOD[1])
    inst.components.combat:SetRange(TUNING.SHADOW_KNIGHT.ATTACK_RANGE, TUNING.SHADOW_KNIGHT.ATTACK_RANGE_LONG)


    inst.LevelUp = noLevelUp

    inst.components.sanityaura.aura = -120 / 60

    inst.components.lootdropper:SetLootSetupFn(lootsetfn_default)

    inst:ListenForEvent("death", Spawnnightlight)

    return inst
end
local function searchfornightlight(inst, target, weapon, multiplier, mount)
    local nightlight = FindEntity(inst, 20, function(ent)
        return ent.prefab == "nightlight"
    end)
    if nightlight then
        return (TUNING.SHADOW_KNIGHT.DAMAGE[1] + 50) / TUNING.SHADOW_KNIGHT.DAMAGE[1]
    end
end
local function ousterability(inst)
    local miasmamanager = TheWorld.components.miasmamanager
    if miasmamanager then
        local x, y, z = inst.Transform:GetWorldPosition()

        miasmamanager:CreateMiasmaAtPoint(x, 0, z)
        miasmamanager:CreateMiasmaAtPoint(x - TILE_SCALE, 0, z - TILE_SCALE)
        miasmamanager:CreateMiasmaAtPoint(x, 0, z - TILE_SCALE)
        miasmamanager:CreateMiasmaAtPoint(x + TILE_SCALE, 0, z - TILE_SCALE)
        miasmamanager:CreateMiasmaAtPoint(x - TILE_SCALE, 0, z)
        miasmamanager:CreateMiasmaAtPoint(x + TILE_SCALE, 0, z)
        miasmamanager:CreateMiasmaAtPoint(x - TILE_SCALE, 0, z + TILE_SCALE)
        miasmamanager:CreateMiasmaAtPoint(x, 0, z + TILE_SCALE)
        miasmamanager:CreateMiasmaAtPoint(x + TILE_SCALE, 0, z + TILE_SCALE)
    end
end
local function ousterfn()
    local inst = commonfn("shadow_knight")
    inst:AddTag("witch_friend")
    if not TheWorld.ismastersim then
        return inst
    end

    inst.components.locomotor.walkspeed = TUNING.SHADOW_KNIGHT.SPEED[1]
    inst.components.health:SetMaxHealth(TUNING.SHADOW_KNIGHT.HEALTH[1])
    inst.components.combat:SetDefaultDamage(TUNING.SHADOW_KNIGHT.DAMAGE[1])
    inst.components.combat:SetAttackPeriod(TUNING.SHADOW_KNIGHT.ATTACK_PERIOD[1])
    inst.components.combat:SetRange(TUNING.SHADOW_KNIGHT.ATTACK_RANGE, TUNING.SHADOW_KNIGHT.ATTACK_RANGE_LONG)
    inst.components.combat.customdamagemultfn = searchfornightlight


    inst.LevelUp = noLevelUp
    inst.components.lootdropper:SetLootSetupFn(lootsetfn_default)

    inst:DoPeriodicTask(30, ousterability)
    return inst
end
local function leaderability(inst)
    if not inst.components.health:IsDead() then
        local x, y, z = inst.Transform:GetWorldPosition()
        inst.follower_walkergroup = inst.follower_walkergroup or {}
        for i = 1, 10 do
            if not inst.follower_walkergroup[i]
                or not inst.follower_walkergroup[i]:IsValid() then
                inst.follower_walkergroup[i] = SpawnPrefab("witch_shadow_knight_walker")
                inst.follower_walkergroup[i].Transform:SetPosition(x + 10 * math.random() - 5, y,
                    z + 10 * math.random() - 5)
                inst.follower_walkergroup[i].persists = false
            end
        end
    end
end
local function leaderfn()
    local inst = commonfn("shadow_knight", nil, witch_shadow_knight_noattackbrain)
    inst:AddTag("witch_friend")
    if not TheWorld.ismastersim then
        return inst
    end

    inst.components.locomotor.walkspeed = TUNING.SHADOW_KNIGHT.SPEED[1]
    inst.components.health:SetMaxHealth(TUNING.SHADOW_KNIGHT.HEALTH[1])
    inst.components.combat:SetDefaultDamage(TUNING.SHADOW_KNIGHT.DAMAGE[1])
    inst.components.combat:SetAttackPeriod(TUNING.SHADOW_KNIGHT.ATTACK_PERIOD[1])
    inst.components.combat:SetRange(TUNING.SHADOW_KNIGHT.ATTACK_RANGE, TUNING.SHADOW_KNIGHT.ATTACK_RANGE_LONG)
    inst.components.combat.customdamagemultfn = searchfornightlight
    inst.components.combat.canattack = false


    inst.LevelUp = noLevelUp
    inst.components.lootdropper:SetLootSetupFn(lootsetfn_default)

    inst:DoPeriodicTask(30, leaderability)
    return inst
end
local asset = createassets("shadow_knight")
local prefab = createprefabs({})
return Prefab("witch_shadow_knight", knightfn, asset, prefab)
, Prefab("witch_shadow_knight_walker", walkerfn, asset, prefab)
, Prefab("witch_shadow_knight_lighter", trackerfn, asset, prefab)
, Prefab("witch_shadow_knight_ouster", ousterfn, asset, prefab)
, Prefab("witch_shadow_knight_leader", leaderfn, asset, prefab)
